﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RuleOfReady.Core.DomainModels.Tiles
{
    /// <summary>
    /// Types of Suits for Mahjong tiles
    /// </summary>
    public enum MahjongSuitType
    {
        Bamboo = 1,
        Character,
        Dot
    }

    /// <summary>
    /// The allowed values of Suit Mahjong Tiles
    /// </summary>
    public enum MahjongSuitNumber
    {
        One = 1,
        Two,
        Three,
        Four,
        Five,
        Six,
        Seven,
        Eight,
        Nine
    }

    /// <summary>
    /// Types of Mahjong Honor Tiles 
    /// </summary>
    public enum MahjongHonorType
    {
        EastWind = 1,
        SouthWind,
        WestWind,
        NorthWind,
        RedDragon,
        WhiteDragon,
        GreenDragon
    }

    /// <summary>
    /// A Mahjong Tile. (This implements IEquatable and IComparable)
    /// </summary>
    public abstract class MahjongTile : IEquatable<MahjongTile>, IComparable<MahjongTile>
    {

        #region Public Methods

        /// <summary>
        /// Override for equality, when checked against something that's not a MahjongTile
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            return this.EqualToImpl(obj as MahjongTile);
        }

        /// <summary>
        /// Override for determining hashcode, must return same value for equal objects
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this.GetHashCodeImpl();
        }

        #region IEquatable Implementation

        /// <summary>
        /// Definition of equality for when compared to another MahjongTile object
        /// </summary>
        /// <param name="other">another MahjongTile</param>
        /// <returns></returns>
        public bool Equals(MahjongTile other)
        {
            return this.EqualToImpl(other);
        }

        #endregion

        #region IComparable Implementation

        /// <summary>
        /// Definition of ordering when compared to another MahjongTile. 
        /// This is used to implement operator overloads.
        /// </summary>
        /// <param name="other">another MahjongTile</param>
        /// <returns></returns>
        public int CompareTo(MahjongTile other)
        {
            return this.CompareToImpl(other);
        }

        #endregion


        #region Operator overloads

        /// <summary>
        /// Definition of '==' for MahjongTiles. Handles null values first.
        /// </summary>
        /// <param name="left">left operand</param>
        /// <param name="right">right operand</param>
        /// <returns></returns>
        public static bool operator ==(MahjongTile left, MahjongTile right)
        {
            bool leftIsNull = Object.ReferenceEquals(left, null);
            bool rightIsNull = Object.ReferenceEquals(right, null);

            if (leftIsNull && rightIsNull)
                return true;
            else if (leftIsNull || rightIsNull)
                return false;
            else
                return left.EqualToImpl(right);
        }

        /// <summary>
        /// Definition of '!=' for Mahjong Tiles
        /// </summary>
        /// <param name="left">left operand</param>
        /// <param name="right">right operand</param>
        /// <returns></returns>
        public static bool operator !=(MahjongTile left, MahjongTile right)
        {
            return !(left == right);
        }

        /// <summary>
        /// Definition of '&lt;' for Mahjong Tiles
        /// </summary>
        /// <param name="left">left operand</param>
        /// <param name="right">right operand</param>
        /// <returns></returns>
        public static bool operator <(MahjongTile left, MahjongTile right)
        {
            return left.CompareTo(right) < 0;
        }

        /// <summary>
        /// Definition of '&gt;' for Mahjong Tiles
        /// </summary>
        /// <param name="left">left operand</param>
        /// <param name="right">right operand</param>
        /// <returns></returns>
        public static bool operator >(MahjongTile left, MahjongTile right)
        {
            return left.CompareTo(right) > 0;
        }

        /// <summary>
        /// Definition of '&lt;=' for Mahjong Tiles
        /// </summary>
        /// <param name="left">left operand</param>
        /// <param name="right">right operand</param>
        /// <returns></returns>
        public static bool operator <=(MahjongTile left, MahjongTile right)
        {
            return left.CompareTo(right) <= 0;
        }

        /// <summary>
        /// Definition of '&gt;=' for Mahjong Tiles
        /// </summary>
        /// <param name="left">left operand</param>
        /// <param name="right">right operand</param>
        /// <returns></returns>
        public static bool operator >=(MahjongTile left, MahjongTile right)
        {
            return left.CompareTo(right) >= 0;
        }

        #endregion

        #endregion

        #region Protected Abstract Members 

        /// <summary>
        /// Abstract method for the implementation of comparing Mahjong Tiles
        /// </summary>
        /// <param name="other">another MahjongTile</param>
        /// <returns></returns>
        protected abstract int CompareToImpl(MahjongTile other);

        /// <summary>
        /// Abstract method for the implementation of Equating Mahjong Tiles
        /// </summary>
        /// <param name="other">another MahjongTile</param>
        /// <returns></returns>
        protected abstract bool EqualToImpl(MahjongTile other);

        /// <summary>
        /// Abstract method for the implementation of getting a hashcode value Mahjong Tiles
        /// </summary>
        /// <param name="other">another MahjongTile</param>
        /// <returns></returns>
        protected abstract int GetHashCodeImpl();

        #endregion

    }

    /// <summary>
    /// A Mahjong Tile that's a suit
    /// </summary>
    public class MahjongSuitTile : MahjongTile
    {

        private readonly IReadOnlyDictionary<MahjongSuitType, string> SuitAbbv =
            new Dictionary<MahjongSuitType, string>()
            {
                {MahjongSuitType.Bamboo, "b"},
                {MahjongSuitType.Character, "c"},
                {MahjongSuitType.Dot, "d"}
            };
        
        #region Public Properties (read only)

        /// <summary>
        /// Type of the suit
        /// </summary>
        public MahjongSuitType SuitType { get; private set; }

        /// <summary>
        /// Number of the suit
        /// </summary>
        public MahjongSuitNumber SuitNumber { get; private set; }

        /// <summary>
        /// Number of the suit, as an int
        /// </summary>
        public int SuitNumberInt { get { return (int)SuitNumber; } }
        /// <summary>
        /// Is this tile a Red Bonus (akidora) Tile?
        /// </summary>
        /// <remarks>
        /// This has no effect on Equality for a mahjong tile
        /// </remarks>
        public bool IsRedBonus { get; private set; }

        #endregion

        #region Constructor

        /// <summary>
        /// Create a new Mahjong suit tile, with a give suit type and number, 
        /// and optionally if the tile is a Red Bonus
        /// </summary>
        /// <param name="suitType">suit of the tile</param>
        /// <param name="suitNumber">number of the tile</param>
        /// <param name="isRedBonus">flag of the</param>
        public MahjongSuitTile(MahjongSuitType suitType, MahjongSuitNumber suitNumber, 
                               bool isRedBonus = false)
        {
            if (!Enum.IsDefined(typeof(MahjongSuitType), suitType))
                throw new ArgumentException(
                    string.Format("'{0}' is not a valid suit type", suitType), "suitType");
            if (!Enum.IsDefined(typeof(MahjongSuitNumber), suitNumber))
                throw new ArgumentException(
                    string.Format("'{0}' is not a valid suit number", suitNumber), "suitNumber");

            this.SuitType = suitType;
            this.SuitNumber = suitNumber;
            this.IsRedBonus = isRedBonus;
        }

        /// <summary>
        /// Create a new Mahjong suit tile, with a give suit type and number, 
        /// and optionally if the tile is a Red Bonus
        /// </summary>
        /// <param name="suitType">suit of the tile</param>
        /// <param name="suitNumber">number of the tile</param>
        /// <param name="isRedBonus">flag of the</param>
        public MahjongSuitTile(MahjongSuitType suitType, int suitNumber, bool isRedBonus = false)
            : this(suitType, (MahjongSuitNumber)suitNumber, isRedBonus) { }

        #endregion

        public override string ToString()
        {
            return string.Format("{1}{0}", this.SuitAbbv[this.SuitType], SuitNumberInt);
        }

        #region Protected Override Members

        /// <summary>
        /// Override for implementation details of equating Mahjong Tiles
        /// </summary>
        /// <param name="other">another Mahjong Tile</param>
        /// <returns></returns>
        protected override bool EqualToImpl(MahjongTile other)
        {
            if (Object.ReferenceEquals(other, null))
                return false;
            if (Object.ReferenceEquals(other, this))
                return true;

            MahjongSuitTile otherSuitTile = other as MahjongSuitTile;
            if (Object.ReferenceEquals(otherSuitTile, null))
                return false;

            return (this.SuitType == otherSuitTile.SuitType) &&
                   (this.SuitNumber == otherSuitTile.SuitNumber);
        }

        /// <summary>
        /// Override for implementation details of getting the hash code value for Mahjong Tiles
        /// </summary>
        /// <param name="other">another Mahjong Tile</param>
        /// <returns></returns>
        protected override int GetHashCodeImpl()
        {
            return this.SuitType.GetHashCode() ^ (this.SuitNumber.GetHashCode() << 4);
        }

        /// <summary>
        /// Override for implementation details of comparing Mahjong Tiles
        /// </summary>
        /// <param name="other">another Mahjong Tile</param>
        /// <returns></returns>
        protected override int CompareToImpl(MahjongTile other)
        {
            if (Object.ReferenceEquals(other, null))
                return 1;
            MahjongSuitTile otherAsSuit = other as MahjongSuitTile;
            if (Object.ReferenceEquals(otherAsSuit, null))
                return -1; //suits are smaller
            else
            {
                int suitCompare = this.SuitType - otherAsSuit.SuitType;
                if (suitCompare != 0)
                    return suitCompare;
                else return this.SuitNumber - otherAsSuit.SuitNumber;
            }
        }

        #endregion
    }

    /// <summary>
    /// A Mahjong tile that's an honor tile
    /// </summary>
    public class MahjongHonorTile : MahjongTile
    {

        private readonly IReadOnlyDictionary<MahjongHonorType, string> HonorAbbv =
        new Dictionary<MahjongHonorType, string>()
            {
                {MahjongHonorType.EastWind, "EW"},
                {MahjongHonorType.SouthWind, "SW"},
                {MahjongHonorType.WestWind, "WW"},
                {MahjongHonorType.NorthWind, "NW"},
                {MahjongHonorType.RedDragon, "RD"},
                {MahjongHonorType.WhiteDragon, "WD"},
                {MahjongHonorType.GreenDragon, "GD"},
            };

        #region Public Properties (read only)

        public MahjongHonorType HonorType { get; private set; }

        #endregion

        #region Constructor

        public MahjongHonorTile(MahjongHonorType honorType)
        {
            if (!Enum.IsDefined(typeof(MahjongHonorType), honorType))
                throw new ArgumentException(
                    string.Format("'{0}' is not a valid honor type", honorType), "honorType");

            this.HonorType = honorType;
        }

        #endregion

        public override string ToString()
        {
            return HonorAbbv[this.HonorType];
        }

        #region Protected Override Members

        /// <summary>
        /// Override for implementation details of equating Mahjong Tiles
        /// </summary>
        /// <param name="other">another Mahjong Tile</param>
        /// <returns></returns>
        protected override bool EqualToImpl(MahjongTile other)
        {
            if (Object.ReferenceEquals(other, null))
                return false;
            if (Object.ReferenceEquals(other, this))
                return true;

            MahjongHonorTile otherHonorTile = other as MahjongHonorTile;
            if (Object.ReferenceEquals(otherHonorTile, null))
                return false;

            return this.HonorType == otherHonorTile.HonorType;
        }

        /// <summary>
        /// Override for implementation details of getting the hash code value for Mahjong Tiles
        /// </summary>
        /// <param name="other">another Mahjong Tile</param>
        /// <returns></returns>
        protected override int GetHashCodeImpl()
        {
            return this.HonorType.GetHashCode();
        }

        /// <summary>
        /// Override for implementation details of comparing Mahjong Tiles
        /// </summary>
        /// <param name="other">another Mahjong Tile</param>
        /// <returns></returns>
        protected override int CompareToImpl(MahjongTile other)
        {
            if (Object.ReferenceEquals(other, null))
                return 1;

            MahjongHonorTile otherAsHonor = other as MahjongHonorTile;
            if (object.ReferenceEquals(otherAsHonor, null))
                return 1; // honors are bigger
            else
                return this.HonorType - otherAsHonor.HonorType;
        }

        #endregion

    }
}
